package com.ht.api.open;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.ht.api.db.Db;
import com.ht.api.db.DbList;
import com.ht.api.db.Entity;
import com.ht.api.db.remote.ApiUtil;
import com.ht.api.db.util.RedisUtil;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 管理所有平台及应用的access_token<br>
 * 将token缓存到redis，并且过期前会自动刷新<br>
 * 外部只管从本类取token使用，不用再关心过期等问题
 * @author asq
 * @createTime 2024年12月17日 12:33:09
 */
@Service
@Slf4j
public class TokenService {
    @Autowired
    private Environment env;
	
    /**
     * 获取指定平台及应用的access_token
     * @param platformId 平台ID
     * @param appId 应用ID
     * @return
     */
    public String getToken(long platformId, String appId) {
		// 是否是生产环境
		boolean isProd = env.matchesProfiles("prod");
		
		// 生产环境从缓存获取，非生产环境从数据库获取
		if (isProd) {
			// 生成缓存key
			String cacheKey = getTokenKey(platformId, appId);
			
			// 获取缓存值
			return RedisUtil.get(cacheKey);
		} else {
			return Db.createSqlParser("token").select("token").where("appid", appId).val(String.class);
		}
    }

    // 定时刷新所有平台及应用在缓存中的token
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次，启动时执行一次
    private void refreshTokens() {
        try {
    		// 是否是生产环境
    		boolean isProd = env.matchesProfiles("prod");
    		if (!isProd) {
    			log.info("[TokenService]非生产环境，不获取token~");
    			return;
    		}
    		
            // 获取所有的开放平台
        	log.info("[TokenService]刷新所有token开始...");
            getPlatformList().stream().forEach(platform -> {
            	// 获取平台配置
        		JSONObject platformConfig = JSONUtil.parseObj(platform.getStr("config"));
        		
        		// 如果平台配置中没有url，说明这个平台不需要获取token，应跳过
        		if (StrUtil.isNotBlank(platformConfig.getByPath("token.url", String.class))) {
	            	// 获取所有的应用
	            	long platformId = platform.getLong("main_id");
					getAppList(platformId).stream().forEach(app -> {
	                	// 获取应用配置
	            		JSONObject appConfig = JSONUtil.parseObj(app.getStr("config"));
	            		String appId = app.getStr("main_id");
		                
		                // 生成每个token的缓存键
		                String cacheKey = getTokenKey(platformId, appId);
		                
		                // 获取过期时间
		                Long ttl = RedisUtil.getExpire(cacheKey);
		                
		                // 如果TTL小于360秒，刷新token
		                if (ttl == null || ttl < 360) {
		                    getAndCacheToken(platformConfig, appConfig, platformId, appId);
		                    log.info("[TokenService]token即将过期，刷新成功：{} - {}", platform.getStr("name"), app.getStr("name"));
		                } else {
		                	log.info("[TokenService]token过期时间还早，不刷新：{} - {}", platform.getStr("name"), app.getStr("name"));
		                }
	            	});
        		}
            });
        	log.info("[TokenService]刷新所有token结束~");
        } catch (Exception e) {
            log.error("[TokenService]刷新Token发生异常：" + e.getMessage(), e);
        }
    }
    
    /**
     * 获取单个token并存入缓存
     * @param paramConfig open_platform表config配置
     * @param appConfig open_app表config配置
     * @param platformId 平台ID
     * @param appId 应用ID
     * @return
     */
    private String getAndCacheToken(JSONObject paramConfig, JSONObject appConfig, long platformId, String appId) {
        try {
            // 解析配置
            JSONObject tokenConfig = paramConfig.getJSONObject("token");
            JSONObject resultConfig = paramConfig.getJSONObject("result");
            
            // 调用接口获取token
            String token = callTokenApi(tokenConfig, resultConfig, appConfig);
            
            // 缓存token
            if (token != null) {
            	int expires = tokenConfig.getInt("expires");
                cacheToken(platformId, appId, expires, token);
            } else {
                log.error("[TokenService]解析Token失败~");
            }
            
            return token;
        } catch (Exception e) {
            log.error("[TokenService]获取Token发生异常：" + e.getMessage(), e);
            return null;
        }
    }

    /**
     * 调用接口获取token
     * @param tokenConfig open_platform表config配置中的token配置
     * @param resultConfig open_platform表config配置中的result配置
     * @param appConfig open_app表config配置json
     * @return
     */
    private String callTokenApi(JSONObject tokenConfig, JSONObject resultConfig, JSONObject appConfig) {
        String url = tokenConfig.getStr("url");
        String type = tokenConfig.getStr("type");
        String tokenPath = tokenConfig.getStr("path");
        
        try {
            String response;
            if ("GET".equalsIgnoreCase(type)) {
            	String fullUrl = getUrlAndParam(tokenConfig, appConfig);
            	response = ApiUtil.get(fullUrl);
            } else {
            	JSONObject body = getBody(tokenConfig, appConfig);
                response = ApiUtil.post(url, body);
            }
            
            // 解析响应
            JSONObject result = JSONUtil.parseObj(response);
            
            // 检查响应码
            String codeField = resultConfig.getStr("code");
            String successCode = resultConfig.getStr("success");
            
            // 如果success有值，则需要code=success，才成功；否则只要返回对象中包含code字段，即成功
            if (StrUtil.isNotBlank(successCode)) {
            	if (!result.containsKey(codeField)) {
            		log.error("[TokenService]获取token失败，第三方平台的返回json中没有{}字段，请检查平台[Result配置]中[code]是否正确~", codeField);
            	}
	            if (result.getStr(codeField).equals(successCode)) {
	                return result.getStr(tokenPath);
	            }
            } else {
            	if (result.containsKey(codeField)) {
            		return result.getStr(tokenPath);
            	}
            }
            
        } catch (Exception e) {
            log.error("[TokenService]Failed to call token API", e);
        }
        return null;
    }
    
    /**
     * POST方式时，获取请求的body
     * @param tokenConfig
     * @param appConfig
     * @return
     */
    private JSONObject getBody(JSONObject tokenConfig, JSONObject appConfig) {
    	JSONObject body = JSONUtil.createObj();
    	
    	JSONArray params = tokenConfig.getJSONArray("param");
    	for (int i = 0; i < params.size(); i++) {
			JSONObject param = params.getJSONObject(i);
			String key = param.getStr("name");
			String val = getParam(appConfig, key);
			body.set(key, val);
		}
    	
    	return body;
    }
    
    /**
     * GET方式时，获取带参数的url
     * @param tokenConfig
     * @param appConfig
     * @return
     */
    private String getUrlAndParam(JSONObject tokenConfig, JSONObject appConfig) {
        String url = tokenConfig.getStr("url");
    	StringBuilder sb = new StringBuilder(url);
    	
    	JSONArray params = tokenConfig.getJSONArray("param");
    	if (!params.isEmpty()) {
    		sb.append("?");
    	}
    	
    	for (int i = 0; i < params.size(); i++) {
			JSONObject param = params.getJSONObject(i);
			String key = param.getStr("name");
			String val = getParam(appConfig, key);
			sb.append(String.format("%s=%s", key, val));
			
			if (i < params.size() - 1) {
				sb.append("&");
			}
		}
    	
    	return sb.toString();
    }
    
    /**
     * 获取参数值
     * @param appConfig
     * @param name
     * @return
     */
    private String getParam(JSONObject appConfig, String name) {
    	JSONArray values = appConfig.getJSONArray("token_param");
    	for (int i = 0; i < values.size(); i++) {
			JSONObject param = values.getJSONObject(i);
			String key = param.getStr("name");
			String value = param.getStr("value");
			if (name.equals(key)) {
				return value;
			}
		}
    	return "";
    }

    /**
     * 缓存token
     * @param platformId 平台ID
     * @param appId 应用ID
     * @param tokenConfig
     * @param token
     */
    private void cacheToken(long platformId, String appId, int expires, String token) {
        // 生成缓存key
        String cacheKey = getTokenKey(platformId, appId);
        
        // 缓存token
        RedisUtil.set(cacheKey, token, expires);
        
        // 同时更新数据库
        Entity tokenEntity = Db.createEntity("token");
        tokenEntity.set("appid", appId);
        tokenEntity.set("token", token);
        tokenEntity.set("update_time", DateUtil.now());
        Db.insert(tokenEntity);
    }

    /**
     * 获取token在缓存中的key
     * @param platformId 平台ID
     * @param appId 应用ID
     * @return
     */
	private String getTokenKey(long platformId, String appId) {
		return String.format("token:%s:%s", platformId, appId);
	}

    /**
     * 查询所有的开放平台
     * @return
     */
	private DbList getPlatformList() {
		return Db.createSqlParser("open_platform").where("config", "is not", null).query();
	}
	
    /**
     * 查询指定开放平台下的应用列表
     * @param pid 开放平台ID
     * @return
     */
	private DbList getAppList(long pid) {
		return Db.createSqlParser("open_app").where("pid", pid).where("status", 1).where("config", "is not", null).query();
	}
}
